package com.kun.peng.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.kun.peng.exception.BadRequestException;
import com.kun.peng.exception.EntityExistException;
import com.kun.peng.system.domain.Menu;
import com.kun.peng.system.domain.Role;
import com.kun.peng.system.domain.User;
import com.kun.peng.system.service.mapstruct.MenuMapStruct;
import com.kun.peng.utils.CacheKey;
import com.kun.peng.utils.QueryHelp;
import com.kun.peng.utils.RedisUtils;
import com.kun.peng.utils.ValidationUtil;
import com.kun.peng.mybatis.base.IBaseService;
import com.kun.peng.system.domain.vo.MenuMetaVo;
import com.kun.peng.system.domain.vo.MenuVo;
import com.kun.peng.system.mapper.MenuMapper;
import com.kun.peng.system.service.MenuService;
import com.kun.peng.system.service.RoleService;
import com.kun.peng.system.service.UserService;
import com.kun.peng.system.service.dto.MenuDto;
import com.kun.peng.system.service.dto.MenuQuery;
import com.kun.peng.system.service.dto.RoleSmallDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created on 2021/11/19.
 *
 * @author xuebaopeng
 * Description
 */
@Slf4j
@Service
@RequiredArgsConstructor
@CacheConfig(cacheNames = "menu")
public class MenuServiceImpl extends IBaseService<MenuMapper, Menu> implements MenuService {


    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private MenuMapStruct menuMapStruct;
    @Override
    public List<MenuDto> queryAll(MenuQuery criteria, Boolean isQuery) throws Exception {
        criteria.sort="menu_sort,asc";//默认按照菜单排序排列
        if (com.kun.peng.utils.StringUtils.isNotBlank(criteria.sort)){//没有排序或者分页数据为空，表示不是分页请求，走列表查询
            IPage<Menu> menuIPage=QueryHelp.pageQuery(criteria,Menu.class,getBaseMapper());
            return menuMapStruct.toDto(menuIPage.getRecords());
        }else {
            return QueryHelp.listQuery(criteria,Menu.class,getBaseMapper());
        }
    }

    @Override
    public MenuDto findMenuById(long id) {
        Menu menu = findById(id).orElseGet(Menu::new);
        ValidationUtil.isNull(menu.getId(),"Menu","id",id);
        return menuMapStruct.toDto(menu);
    }

    @Override
    public void create(Menu menu) {
        if (getBaseMapper().findByTitle(menu.getTitle())!=null){
          throw new EntityExistException(Menu.class,"title",menu.getTitle());
        }
        if(StringUtils.isNotBlank(menu.getComponentName())){
            if(getBaseMapper().findByComponentName(menu.getComponentName()) != null){
                throw new EntityExistException(Menu.class,"componentName",menu.getComponentName());
            }
        }
        if(menu.getPid().equals(0L)){
            menu.setPid(null);
        }
        if(menu.getIframe()){
            String http = "http://", https = "https://";
            if (!(menu.getPath().toLowerCase().startsWith(http)||menu.getPath().toLowerCase().startsWith(https))) {
                throw new BadRequestException("外链必须以http://或者https://开头");
            }
        }
        save(menu);
        // 计算子节点数目
        menu.setSubCount(0);
        // 更新父节点菜单数目
        updateSubCnt(menu.getPid());
    }

    private void updateSubCnt(Long menuId){
        if(menuId != null){
            int count = getBaseMapper().countByPid(menuId);
            getBaseMapper().updateSubCntById(count, menuId);
        }
    }

    @Override
    public void update(Menu menu) {
        if(menu.getId().equals(menu.getPid())) {
            throw new BadRequestException("上级不能为自己");
        }
        //查询菜单没有创建
        Menu menuOld = findById(menu.getId()).orElseGet(Menu::new);
        ValidationUtil.isNull(menuOld.getId(),"Permission","id",menu.getId());
        if(menu.getIframe()){
            String http = "http://", https = "https://";
            if (!(menu.getPath().toLowerCase().startsWith(http)||menu.getPath().toLowerCase().startsWith(https))) {
                throw new BadRequestException("外链必须以http://或者https://开头");
            }
        }
        Menu menuOld1 = getBaseMapper().findByTitle(menu.getTitle());
        if(menuOld1 != null && !menuOld1.getId().equals(menu.getId())){
            throw new EntityExistException(Menu.class,"title",menu.getTitle());
        }

        if(menu.getPid().equals(0L)){
            menu.setPid(null);
        }
       // 记录的父节点ID
        Long oldPid = menuOld.getPid();
        Long newPid = menu.getPid();
        if(StringUtils.isNotBlank(menu.getComponentName())){
            menuOld1 = getBaseMapper().findByComponentName(menu.getComponentName());
            if(menuOld1 != null && !menuOld1.getId().equals(menu.getId())){
                throw new EntityExistException(Menu.class,"componentName",menu.getComponentName());
            }
        }
        menuOld.setTitle(menu.getTitle());
        menuOld.setComponent(menu.getComponent());
        menuOld.setPath(menu.getPath());
        menuOld.setIcon(menu.getIcon());
        menuOld.setIframe(menu.getIframe());
        menuOld.setPid(menu.getPid());
        menuOld.setMenuSort(menu.getMenuSort());
        menuOld.setCache(menu.getCache());
        menuOld.setHidden(menu.getHidden());
        menuOld.setComponentName(menu.getComponentName());
        menuOld.setPermission(menu.getPermission());
        menuOld.setType(menu.getType());
        saveOrUpdate(menuOld);

        // 计算父级菜单节点数目
        updateSubCnt(oldPid);
        updateSubCnt(newPid);
        // 清理缓存
        delCaches(menu.getId());

    }

    @Override
    public Set<Menu> getChildMenus(List<Menu> menuList, Set<Menu> menuSet) {
        for (Menu menu : menuList) {
            menuSet.add(menu);
            List<Menu> menus = getBaseMapper().findByPid(menu.getId());
            if(menus!=null && menus.size()!=0){
                getChildMenus(menus, menuSet);
            }
        }
        return menuSet;
    }

    @Override
    public List<MenuDto> buildTree(List<MenuDto> menuDtos) {
        List<MenuDto> trees = new ArrayList<>();
        Set<Long> ids = new HashSet<>();
        for (MenuDto menuDTO : menuDtos) {
            if (menuDTO.getPid() == null) {
                trees.add(menuDTO);
            }
            for (MenuDto it : menuDtos) {
                if (menuDTO.getId().equals(it.getPid())) {
                    if (menuDTO.getChildren() == null) {
                        menuDTO.setChildren(new ArrayList<>());
                    }
                    menuDTO.getChildren().add(it);
                    ids.add(it.getId());
                }
            }
        }
        if(trees.size() == 0){
            trees = menuDtos.stream().filter(s -> !ids.contains(s.getId())).collect(Collectors.toList());
        }
        return trees;
    }

    @Override
    public List<MenuVo> buildMenus(List<MenuDto> menuDtos) {
        List<MenuVo> list = new LinkedList<>();
        menuDtos.forEach(menuDTO -> {
                    if (menuDTO!=null){
                        List<MenuDto> menuDtoList = menuDTO.getChildren();
                        MenuVo menuVo = new MenuVo();
                        menuVo.setName(ObjectUtil.isNotEmpty(menuDTO.getComponentName())  ? menuDTO.getComponentName() : menuDTO.getTitle());
                        // 一级目录需要加斜杠，不然会报警告
                        menuVo.setPath(menuDTO.getPid() == null ? "/" + menuDTO.getPath() :menuDTO.getPath());
                        menuVo.setHidden(menuDTO.getHidden());
                        // 如果不是外链
                        if(!menuDTO.getIframe()){
                            if(menuDTO.getPid() == null){
                                menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?"Layout":menuDTO.getComponent());
                                // 如果不是一级菜单，并且菜单类型为目录，则代表是多级菜单
                            }else if(menuDTO.getType() == 0){
                                menuVo.setComponent(StringUtils.isEmpty(menuDTO.getComponent())?"ParentView":menuDTO.getComponent());
                            }else if(StringUtils.isNoneBlank(menuDTO.getComponent())){
                                menuVo.setComponent(menuDTO.getComponent());
                            }
                        }
                        menuVo.setMeta(new MenuMetaVo(menuDTO.getTitle(),menuDTO.getIcon(),!menuDTO.getCache()));
                        if(CollectionUtil.isNotEmpty(menuDtoList)){
                            menuVo.setAlwaysShow(true);
                            menuVo.setRedirect("noredirect");
                            menuVo.setChildren(buildMenus(menuDtoList));
                            // 处理是一级菜单并且没有子菜单的情况
                        } else if(menuDTO.getPid() == null){
                            MenuVo menuVo1 = new MenuVo();
                            menuVo1.setMeta(menuVo.getMeta());
                            // 非外链
                            if(!menuDTO.getIframe()){
                                menuVo1.setPath("index");
                                menuVo1.setName(menuVo.getName());
                                menuVo1.setComponent(menuVo.getComponent());
                            } else {
                                menuVo1.setPath(menuDTO.getPath());
                            }
                            menuVo.setName(null);
                            menuVo.setMeta(null);
                            menuVo.setComponent("Layout");
                            List<MenuVo> list1 = new ArrayList<>();
                            list1.add(menuVo1);
                            menuVo.setChildren(list1);
                        }
                        list.add(menuVo);
                    }
                }
        );
        return list;
    }

    @Override
    public Menu findOne(Long id) {
        Menu menu = findById(id).orElseGet(Menu::new);
        ValidationUtil.isNull(menu.getId(),"Menu","id",id);
        return menu;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Set<Menu> menuSet) {
        for (Menu menu : menuSet) {
            // 清理缓存
            delCaches(menu.getId());
            roleService.untiedMenu(menu.getId());
            getBaseMapper().deleteById(menu.getId());
            updateSubCnt(menu.getPid());
        }
    }

    @Override
    public void download(List<MenuDto> queryAll, HttpServletResponse response) throws IOException {

    }

    @Override
    public List<MenuDto> getMenus(Long pid) {
        List<Menu> menus;
        if(pid != null && !pid.equals(0L)){
            menus = getBaseMapper().findByPid(pid);
        } else {
            menus = getBaseMapper().findByPidIsNull();
        }
        return menuMapStruct.toDto(menus);
    }

    @Override
    public List<MenuDto> getSuperior(MenuDto menuDto, List<Menu> menus) {
        if(menuDto.getPid() == null){
            menus.addAll(getBaseMapper().findByPidIsNull());
            return menuMapStruct.toDto(menus);
        }
        menus.addAll(getBaseMapper().findByPid(menuDto.getPid()));
        return getSuperior(findMenuById(menuDto.getPid()), menus);
    }

    /**
     * 用户角色改变时需清理缓存
     * @param currentUserId /
     * @return /
     */
    @Override
    @Cacheable(key = "'user:' + #p0")
    public List<MenuDto> findByUser(Long currentUserId) {
        List<RoleSmallDto> roles = roleService.findByUsersId(currentUserId);
        Set<Long> roleIds = roles.stream().map(RoleSmallDto::getId).collect(Collectors.toSet());
        LinkedHashSet<Menu> menus = getBaseMapper().findByRoleIdsAndTypeNot(roleIds, 2);
        return menus.stream().map(menuMapStruct::toDto).collect(Collectors.toList());
    }

    @Override
    public Set<Menu> selectMenusByRoleId(Long roleId) {
        return getBaseMapper().selectMenusByRoleId(roleId);
    }

    /**
     * 清理缓存
     * @param id 菜单ID
     */
    public void delCaches(Long id){
        try {
            List<User> users = userService.findByMenuId(id);
            redisUtils.del(CacheKey.MENU_ID + id);
            redisUtils.delByKeys(CacheKey.MENU_USER, users.stream().map(User::getId).collect(Collectors.toSet()));
            // 清除 Role 缓存
            List<Role> roles = roleService.findInMenuId(new ArrayList<Long>(){{
                add(id);
            }});
            redisUtils.delByKeys(CacheKey.ROLE_ID, roles.stream().map(Role::getId).collect(Collectors.toSet()));
        }catch (Exception e){
            log.error("清理缓存失败",e);
        }
    }
}
